﻿/*
Copyright (c) 2012 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/

#region Usings
using System;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
#endregion

namespace Utilities.DataTypes.ExtensionMethods
{
    /// <summary>
    /// 日期时间扩展方
    /// </summary>
    public static class DateTimeExtensions
    {
        #region Extension Methods

        #region 添加周

        /// <summary>
        /// 向日期添加周的数
        /// </summary>
        /// <param name="Date">日期输入</param>
        /// <param name="NumberOfWeeks">若要添加的周数</param>
        /// <returns>添加日期之后的周数</returns>
        public static DateTime AddWeeks(this DateTime Date,int NumberOfWeeks)
        {
            return Date.AddDays(NumberOfWeeks * 7);
        }

        #endregion

        #region Age

        /// <summary>
        /// 计算基于提供的日期的年龄
        /// </summary>
        /// <param name="Date">Birth date</param>
        /// <param name="CalculateFrom">Date to calculate from</param>
        /// <returns>The total age in years</returns>
        public static int Age(this DateTime Date, DateTime CalculateFrom = default(DateTime))
        {
            if (CalculateFrom.IsDefault())
                CalculateFrom = DateTime.Now;
            return (CalculateFrom-Date).Years();
        }

        #endregion

        #region 以天开始

        /// <summary>
        /// 返回以天为开始。舍去小时等
        /// </summary>
        /// <param name="Input">Input date</param>
        /// <returns>The beginning of the day</returns>
        public static DateTime BeginningOfDay(this DateTime Input)
        {
            return new DateTime(Input.Year, Input.Month, Input.Day, 0, 0, 0);
        }

        #endregion

        #region ConvertToTimeZone

        /// <summary>
        /// 将日期时间转换为特定的时区
        /// </summary>
        /// <param name="Date">DateTime to convert</param>
        /// <param name="TimeZone">Time zone to convert to</param>
        /// <returns>The converted DateTime</returns>
        public static DateTime ConvertToTimeZone(this DateTime Date, TimeZoneInfo TimeZone)
        {
            return TimeZoneInfo.ConvertTime(Date, TimeZone);
        }

        #endregion

        #region 月份中的天数

        /// <summary>
        /// 返回月份中的天数
        /// </summary>
        /// <param name="Date">Date to get the month from</param>
        /// <returns>The number of days in the month</returns>
        public static int DaysInMonth(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return Date.LastDayOfMonth().Day;
        }

        #endregion

        #region 月余天

        /// <summary>
        /// 获取基于传入的日期月份离开的天数
        /// </summary>
        /// <param name="Date">The date to check against</param>
        /// <returns>The number of days left in a month</returns>
        public static int DaysLeftInMonth(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return Thread.CurrentThread.CurrentCulture.Calendar.GetDaysInMonth(Date.Year, Date.Month) - Date.Day;
        }

        #endregion

        #region 在年余天

        /// <summary>
        /// 获取基于传入的日期一年离开的天数
        /// </summary>
        /// <param name="Date">The date to check against</param>
        /// <returns>The number of days left in a year</returns>
        public static int DaysLeftInYear(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return Thread.CurrentThread.CurrentCulture.Calendar.GetDaysInYear(Date.Year) - Date.DayOfYear;
        }

        #endregion

        #region 在周中余天

        /// <summary>
        /// 获取在一周内离开的天数
        /// </summary>
        /// <param name="Date">The date to check against</param>
        /// <returns>The number of days left in a week</returns>
        public static int DaysLeftInWeek(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return 7 - ((int)Date.DayOfWeek + 1);
        }

        #endregion

        #region EndOfDay

        /// <summary>
        /// 返回一天结束
        /// </summary>
        /// <param name="Input">Input date</param>
        /// <returns>The end of the day</returns>
        public static DateTime EndOfDay(this DateTime Input)
        {
            return new DateTime(Input.Year, Input.Month, Input.Day, 23, 59, 59);
        }

        #endregion

        #region FirstDayOfMonth

        /// <summary>
        /// 返回基于日期月的第一天的日期
        /// </summary>
        /// <param name="Date">Date to get the first day of the month from</param>
        /// <returns>The first day of the month</returns>
        public static DateTime FirstDayOfMonth(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return new DateTime(Date.Year, Date.Month, 1);
        }

        #endregion

        #region FirstDayOfQuarter

        /// <summary>
        /// Returns the first day of a quarter based on the date sent in
        /// </summary>
        /// <param name="Date">Date to get the first day of the quarter from</param>
        /// <param name="Quarter1Start">Beginning of the first quarter (defaults to the beginning of the year)</param>
        /// <returns>The first day of the quarter</returns>
        public static DateTime FirstDayOfQuarter(this DateTime Date, DateTime Quarter1Start = default(DateTime))
        {
            Date.ThrowIfNull("Date");
            if (Quarter1Start.IsDefault())
                Quarter1Start = Date.FirstDayOfYear();
            if (Date.Between(Quarter1Start, Quarter1Start.AddMonths(3).AddDays(-1).EndOfDay()))
                return Quarter1Start.Date;
            else if (Date.Between(Quarter1Start.AddMonths(3), Quarter1Start.AddMonths(6).AddDays(-1).EndOfDay()))
                return Quarter1Start.AddMonths(3).Date;
            else if (Date.Between(Quarter1Start.AddMonths(6), Quarter1Start.AddMonths(9).AddDays(-1).EndOfDay()))
                return Quarter1Start.AddMonths(6).Date;
            return Quarter1Start.AddMonths(9).Date;
        }

        #endregion

        #region FirstDayOfWeek

        /// <summary>
        /// Returns the first day of a week based on the date sent in
        /// </summary>
        /// <param name="Date">Date to get the first day of the week from</param>
        /// <param name="CultureInfo">The culture to use (defaults to current culture)</param>
        /// <returns>The first day of the week</returns>
        public static DateTime FirstDayOfWeek(this DateTime Date,CultureInfo CultureInfo=null)
        {
            Date.ThrowIfNull("Date");
            return Date.AddDays(CultureInfo.NullCheck(CultureInfo.CurrentCulture).DateTimeFormat.FirstDayOfWeek - Date.DayOfWeek).Date;
        }

        #endregion

        #region FirstDayOfYear

        /// <summary>
        /// Returns the first day of a year based on the date sent in
        /// </summary>
        /// <param name="Date">Date to get the first day of the year from</param>
        /// <returns>The first day of the year</returns>
        public static DateTime FirstDayOfYear(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return new DateTime(Date.Year, 1, 1);
        }

        #endregion

        #region FromUnixTime

        /// <summary>
        /// 返回 Unix 基于日期作为一个 DateTime 对象
        /// </summary>
        /// <param name="Date">Unix date to convert</param>
        /// <returns>The Unix Date in DateTime format</returns>
        public static DateTime FromUnixTime(this int Date)
        {
            return new DateTime((Date * TimeSpan.TicksPerSecond) + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks, DateTimeKind.Utc);
        }

        /// <summary>
        /// 返回 Unix 基于日期作为一个 DateTime 对象
        /// </summary>
        /// <param name="Date">Unix date to convert</param>
        /// <returns>The Unix Date in DateTime format</returns>
        public static DateTime FromUnixTime(this long Date)
        {
            return new DateTime((Date * TimeSpan.TicksPerSecond) + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks, DateTimeKind.Utc);
        }

        #endregion

        #region IsInFuture

        /// <summary>
        /// 确定日期是否在未来一段时间
        /// </summary>
        /// <param name="Date">Date to check</param>
        /// <returns>True if it is, false otherwise</returns>
        public static bool IsInFuture(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return DateTime.Now < Date;
        }

        #endregion

        #region IsInPast

        /// <summary>
        /// 确定是否该日期是在过去一段时间
        /// </summary>
        /// <param name="Date">Date to check</param>
        /// <returns>True if it is, false otherwise</returns>
        public static bool IsInPast(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return DateTime.Now > Date;
        }

        #endregion

        #region IsToday

        /// <summary>
        /// Is this today?
        /// </summary>
        /// <param name="Date">Date to check</param>
        /// <returns>True if it is, false otherwise</returns>
        public static bool IsToday(this DateTime Date)
        {
            return (Date.Date.BeginningOfDay() == DateTime.Today);
        }

        #endregion

        #region IsWeekDay

        /// <summary>
        /// Determines if this is a week day
        /// </summary>
        /// <param name="Date">Date to check against</param>
        /// <returns>Whether this is a week day or not</returns>
        public static bool IsWeekDay(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return (int)Date.DayOfWeek < 6 && (int)Date.DayOfWeek > 0;
        }

        #endregion

        #region IsWeekEnd

        /// <summary>
        /// Determines if this is a week end
        /// </summary>
        /// <param name="Date">Date to check against</param>
        /// <returns>Whether this is a week end or not</returns>
        public static bool IsWeekEnd(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return !IsWeekDay(Date);
        }

        #endregion

        #region LastDayOfMonth

        /// <summary>
        /// 返回基于发送日期的月份的最后一天
        /// </summary>
        /// <param name="Date">Date to get the last day from</param>
        /// <returns>The last day of the month</returns>
        public static DateTime LastDayOfMonth(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return Date.AddMonths(1).FirstDayOfMonth().AddDays(-1).Date;
        }

        #endregion

        #region LastDayOfQuarter

        /// <summary>
        /// Returns the last day of a quarter based on the date sent in
        /// </summary>
        /// <param name="Date">Date to get the last day of the quarter from</param>
        /// <param name="Quarter1Start">Beginning of the first quarter (defaults to the beginning of the year)</param>
        /// <returns>The last day of the quarter</returns>
        public static DateTime LastDayOfQuarter(this DateTime Date, DateTime Quarter1Start = default(DateTime))
        {
            Date.ThrowIfNull("Date");
            if (Quarter1Start.IsDefault())
                Quarter1Start = Date.FirstDayOfYear();
            if (Date.Between(Quarter1Start, Quarter1Start.AddMonths(3).AddDays(-1).EndOfDay()))
                return Quarter1Start.AddMonths(3).AddDays(-1).Date;
            else if (Date.Between(Quarter1Start.AddMonths(3), Quarter1Start.AddMonths(6).AddDays(-1).EndOfDay()))
                return Quarter1Start.AddMonths(6).AddDays(-1).Date;
            else if (Date.Between(Quarter1Start.AddMonths(6), Quarter1Start.AddMonths(9).AddDays(-1).EndOfDay()))
                return Quarter1Start.AddMonths(9).AddDays(-1).Date;
            return Quarter1Start.AddYears(1).AddDays(-1).Date;
        }

        #endregion

        #region LastDayOfWeek

        /// <summary>
        /// Returns the last day of a week based on the date sent in
        /// </summary>
        /// <param name="Date">Date to get the last day of the week from</param>
        /// <param name="CultureInfo">The culture to use (defaults to current culture)</param>
        /// <returns>The last day of the week</returns>
        public static DateTime LastDayOfWeek(this DateTime Date, CultureInfo CultureInfo = null)
        {
            Date.ThrowIfNull("Date");
            return Date.FirstDayOfWeek(CultureInfo.NullCheck(CultureInfo.CurrentCulture)).AddDays(6);
        }

        #endregion

        #region LastDayOfYear

        /// <summary>
        /// Returns the last day of the year based on the date sent in
        /// </summary>
        /// <param name="Date">Date to get the last day from</param>
        /// <returns>The last day of the year</returns>
        public static DateTime LastDayOfYear(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return new DateTime(Date.Year, 12, 31);
        }

        #endregion

        #region LocalTimeZone

        /// <summary>
        /// Gets the local time zone
        /// </summary>
        /// <param name="Date">Date object</param>
        /// <returns>The local time zone</returns>
        public static TimeZoneInfo LocalTimeZone(this DateTime Date)
        {
            return TimeZoneInfo.Local;
        }

        #endregion

        #region ToUnix

        /// <summary>
        /// Returns the date in Unix format
        /// </summary>
        /// <param name="Date">Date to convert</param>
        /// <returns>The date in Unix format</returns>
        public static int ToUnix(this DateTime Date)
        {
            Date.ThrowIfNull("Date");
            return (int)((Date.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks / TimeSpan.TicksPerSecond);
        }

        #endregion

        #region SetTime

        /// <summary>
        /// 设置某一特定日期的时间部分
        /// </summary>
        /// <param name="Date">Date input</param>
        /// <param name="Hour">Hour to set</param>
        /// <param name="Minutes">Minutes to set</param>
        /// <param name="Seconds">Seconds to set</param>
        /// <returns>Sets the time portion of the specified date</returns>
        public static DateTime SetTime(this DateTime Date, int Hour, int Minutes, int Seconds)
        {
            return Date.SetTime(new TimeSpan(Hour, Minutes, Seconds));
        }

        /// <summary>
        /// 设置某一特定日期的时间部分
        /// </summary>
        /// <param name="Date">Date input</param>
        /// <param name="Time">Time to set</param>
        /// <returns>Sets the time portion of the specified date</returns>
        public static DateTime SetTime(this DateTime Date, TimeSpan Time)
        {
            return Date.Date.Add(Time);
        }

        #endregion

        #region UTCOffset

        /// <summary>
        /// Gets the UTC offset
        /// </summary>
        /// <param name="Date">Date to get the offset of</param>
        /// <returns>UTC offset</returns>
        public static double UTCOffset(this DateTime Date)
        {
            return (Date - Date.ToUniversalTime()).TotalHours;
        }

        #endregion

        /// <summary>
        /// 计算两个日期的时间间隔
        /// </summary>
        /// <param name="Date">第一个日期和时间</param>
        /// <param name="DateNext">要比较的日期时间</param>
        /// <returns></returns>
        public static string DateDiff(this DateTime Date, DateTime DateNext)
        {
            string dateDiff = null;
            TimeSpan ts1 = new TimeSpan(Date.Ticks);
            TimeSpan ts2 = new TimeSpan(DateNext.Ticks);
            TimeSpan ts = ts1.Subtract(ts2).Duration();
            string days = ts.Days.ToString();
            string hours = ts.Hours.ToString();
            string minutes = ts.Minutes.ToString();
            string seconds = ts.Seconds.ToString();

            if (days != "0")
            {
                dateDiff += "" + ts.Days.ToString() + "天";
            }
            if (hours != "0")
            {
                dateDiff += "" + ts.Hours.ToString() + "小时";
            }
            if (minutes != "0")
            {
                dateDiff += "" + ts.Minutes.ToString() + "分钟";
            }
            if (seconds != "0")
            {
                dateDiff += "" + ts.Seconds.ToString() + "秒";
            }

            //dateDiff = ts.Days.ToString() + "天"
            //+ ts.Hours.ToString() + "小时"
            //+ ts.Minutes.ToString() + "分钟"
            //+ ts.Seconds.ToString() + "秒";

            return dateDiff;
        }

        #endregion
    }
}